package concurrent;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Main Klasse welche die Berechnung fuer die xml file(s) macht
 * @author Patrick Bender, Lukas Subel
 */
public class Main {
	
	/**
	 * Der executor kuemmert sich um die Ausfuehrung der Splitter-Threads
	 */
	private static ExecutorService executor;
	
	/**
	 * Der hilfsExecutor kuemmert sich um die Ausfuehrung der Kontroll-Threads zur eventuellen Beschleunigung der Splitter-Threads 
	 */
	private static ExecutorService hilfsExecutor;
	
	/**
	 * Gibt an in wie viele Teile eine .xml Datei zum Parsen geteilt wird.
	 */
	private static int divide = 4;
	
	/**
	 * Test method; used to test if parser and Algorithm are running correctly. 
	 * @param debug When true prints additional debugging information.
	 * @param url The location of the .xml-file(s).
	 * @param expected The expected result.
	 * @throws FileNotFoundException if the .xml wasn't found.
	 * @throws Exception if the calculated result wasn't equal to the expected and debug was set.
	 */
	private static int test (boolean debug, int expected, String... url) throws Exception {
		long begin = System.currentTimeMillis();
		
		//Executor initialisieren
		executor = Executors.newFixedThreadPool(url.length*divide);
		hilfsExecutor = Executors.newCachedThreadPool();
		
		//Hier wird unsere Liste aller Maschen gespeichert
		//Diese Variable darf nur geaendert werden sofern man das implizite Lock haelt!
		//Ausserdem muss das implizite lock gehalten um lock-races bei den Maschen zu vermeiden
		HashSet<Masche> listeAllerMaschen = new HashSet<Masche>();
		
		// A+B) Parsen und Vorinitialisieren der Masche
		long parseTime = System.currentTimeMillis();

		//Initialisiere Variablen fuer den Parser
		CyclicBarrier barrier = new CyclicBarrier(url.length*divide);
		HashMap<Integer,Node> idNodeMap = new HashMap<Integer,Node>();
		HashMap<Integer,Masche> capacityMascheMap = new HashMap<Integer,Masche>();
		CountDownLatch parserLatch = new CountDownLatch(url.length*divide);

		//Initialisiere fuer jede mitgegebene .xml "divide"(Variable siehte weiter oben) eigene
		//Parser-Threads mit "divide" gleichen Teilen der xml Datei und uebergibt diese an den Executor
		for (int i = 0; i < url.length; i++) {
			
			//Enhaelt den Kompletten Text der aktuellen .xml Datei um zu ueberpruefen 
			//ob der <graph> und der </graph> Tag vorhanden sind
			StringBuffer completeData = new StringBuffer();
			//Enthaelt ca. ein "divide"tel(Variable siehte weiter oben) des Textes der 
			//aktuellen .xml Datei zur uebergabe an den Parser-Thread
			String data;
			
			//Oeffne die Datei und erstelle das Buffer-Array
			File file = new File(url[i]);
			FileReader fr = new FileReader(file);
			char[] arrayBuffer = new char[(int)(file.length()/divide+5)];
			
			//Gibt die Anzahl der gelesenen Zeichen an
			int read = 0;
			
			//Temp enhaelt in der Schleife den Rest des vorherigen String. Wobei der Rest der Teil
			//des Temp String ist der nach dem letzten </node> bzw </edge> ist
			StringBuffer temp = new StringBuffer();
			
			while ( (read = fr.read(arrayBuffer)) != -1)
			{
				//Temp enhaelt dannach den Rest und den neu eingelesenen Teil
				temp.append(arrayBuffer,0,read);
				//completeData wird nach und nach mit der gesamten .xml gefuellt
				completeData.append(arrayBuffer,0,read);
				//Wir bestimmen das letzte Vorkommen von </node> und </edge>
				int a = temp.lastIndexOf("</node>");
				int b = temp.lastIndexOf("</edge>");
				//Kommen beide nicht vor ignorieren wir diesen eingelesenen Teil
				if (a != -1 || b != -1) {
					//Data ist der fuer den Parser verarbeitbare Teil des eingelesenen String  
					data = a > b ? temp.substring(0,a+7) : temp.substring(0,b+7); //7 ist die Laenge von </node> bzw </edge>
					//Temp enhaelt den abgeschnittenen Rest
					temp = a > b ? temp.delete(0, a+7) : temp.delete(0, b+7); //7 ist die Laenge von </node> bzw </edge>
					//Dem Executor wird der Parser-Thread uebergeben
					executor.execute( new Parser(data,idNodeMap,capacityMascheMap,barrier,parserLatch) );
				}
				else
					executor.execute( new Parser("",idNodeMap,capacityMascheMap,barrier,parserLatch) );
			}
			//Nach dem Starten der Parser-Threads fuer die Datei ueberprueft der Main-Thread ob 
			//<graph> und </graph> Tag gesetzt sind
			if (completeData.indexOf("<graph>") == -1 || completeData.lastIndexOf("</graph>") == -1)
				throw new Exception("Fehlendes <graph> oder </graph>");
		}
		//Dannach wird gewartet bis alle Parser mit ihrer Arbeit fertig sind, d.h. decrementiert haben
		parserLatch.await();
		//Und dann wird die Liste aller Maschen gesetzt
		listeAllerMaschen.addAll(capacityMascheMap.values());
		
		//Hier sind wir fertig mit dem Parsen 
		parseTime = System.currentTimeMillis() - parseTime;
		if (debug) System.out.println("Datei: " + Arrays.deepToString(url) );
		if (debug) System.out.println("Schritt A+B: " + listeAllerMaschen);
			
		//C) Splitten der Maschen
		long cutTime = System.currentTimeMillis();

		//Initialisieren der Splitter-Thread Variablen
		
		//Zaehlt wie viele Splitter-Threads aktuell am laufen sind und laesst den Main-Thread auf 
		//terminierung dieser warten
		CountUpAndDownLatch cutLatch = new CountUpAndDownLatch(0);
		
		//Der Main-Thread startet zu Beginn fuer jede Masche einen Splitter-Thread und inkrementiert
		//davor fuer jeden gestarteten Thread den cutLatch
		synchronized (listeAllerMaschen) {
			for (Masche act : listeAllerMaschen) {
				cutLatch.increase();
				executor.execute( new Splitter(act,executor,hilfsExecutor,listeAllerMaschen,cutLatch) );
			}
		}
		//Warten bis alle Splitter-Threads beendet sind
		cutLatch.await();
		
		//Hier sind wir fertig mit dem zerteilen der Maschen
		cutTime = System.currentTimeMillis() - cutTime;
		if (debug) System.out.println("Schritt C: " + listeAllerMaschen);
		if (debug) System.out.println("Schritt C: Es gibt nichts mehr zu reduzieren");
	
		//D) Zaehlen der Maschen
		if (debug) System.out.println("Schritt D: Es gibt " + listeAllerMaschen.size() + " verschiedene Knoten");
		if (debug && listeAllerMaschen.size() != expected) throw new Exception("Expected " + expected + " but was " + listeAllerMaschen.size());
		
		//E) Frau Moerkel gratulieren
		if (debug) System.out.println("Schritt E: Herzlichen Glückwunsch zur Wiederwahl Frau Mörkel");
		if (debug) System.out.println("Brauchte: " + (System.currentTimeMillis() - begin) + "ms, davon " + parseTime + "ms zum Einlesen und " + cutTime + "ms zum Teilen");
		if (debug) System.out.println();

		//Executor herunterfahren
		executor.shutdownNow();
		hilfsExecutor.shutdownNow();
		
		//Das Ergebnis zurueckgeben
		return listeAllerMaschen.size();
	}

	/**
	 * Main Method; used to start the tests with existing .xml file(s)
	 * @param args enhaelt Liste der zu bearbeitenden xml files
	 */
	public static void main(String[] args) {
/*
		try {
			//Wir brauchen (Anzahl der Dateien * divide) Threads
			executor = Executors.newFixedThreadPool(args.length*divide);
			//HilfsThreads werden einfach nach bedarf in ihrer groese automatisch erweitert. 
			//Kann niemals mehr Threads als executor haben da jeder Splitter Thread max einen Kontroll-
			//Thread starten kann
			hilfsExecutor = Executors.newCachedThreadPool();
			if (args.length > 0) {
				int test = test(false, 0, args);
				executor.shutdownNow();
				hilfsExecutor.shutdownNow();
				System.exit( test );		
			}
			executor.shutdownNow();
			hilfsExecutor.shutdownNow();
			System.exit(0);
		}
		catch (Exception e) { 
			System.exit(-1); 
		}
*/

		//Debug Kram von uns
		try {
			long debugtime = System.currentTimeMillis();
			long testzahl = 500;

			for (int i = 1; i <= testzahl; i++) {
				long testtime = System.currentTimeMillis();
				//int zahl = test(false,7,"testsuite/multiple/graph03_a.xml", "testsuite/multiple/graph03_b.xml", "testsuite/multiple/graph03_c.xml");
				//int zahl = test(true,1,"testsuite/spice/graph10.xml");
				//int zahl = test("testsuite/multiple/graph01_a.xml", "testsuite/multiple/graph01_b.xml");
				//int zahl = test(false,5000,"Milchstrasse_generated_5000.xml");
				//int zahl = test(false,1,"milliongraph_1.xml"); //benoetigt 1GB RAM
				//int zahl = test(false,501,"test-cases/500sequential_501.xml");
				//int zahl = test("test-cases/TestGraph11.xml");
				//int zahl = test(false,0,"testsuite/multiple/graph05_a.xml", "testsuite/multiple/graph05_b.xml", "testsuite/multiple/graph05_c.xml", "testsuite/multiple/graph05_d.xml");
				int zahl = test(false,666,"testsuite/crack/germany.xml");
				//int zahl = test("C:\\Users\\IBKR\\Downloads\\testsuite\\spice\\graph19.xml");
				//int zahl = test(true,5,"testsuite/speed/graph15.xml");
				//int zahl = test(false,0,"testsuite/syntax/graph01.xml");
				//int zahl = test(false,4,"test-cases/TestGraph4.xml");
				//int zahl = test(false,8,"test-cases/Energienetz8.xml");
				//int zahl = test(false,13,"test-cases/Neuschleswegien13.xml");
				//int zahl = test("test-cases/GraphausderVorlesung_8.xml");
				System.out.println("Versuch " + i + ", " + (System.currentTimeMillis() - testtime) + "ms. Erg: " + zahl);
			}
			System.out.println( (System.currentTimeMillis() - debugtime) + "ms fuer " + testzahl + " Versuche");
			System.out.println( ( ((System.currentTimeMillis() - debugtime) / (double)testzahl) ) + "ms im Durchschnitt");
		}
		catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}

		
	}

}